#include "../fmt.h"

namespace sfc::fmt {

#define U(n) u64(1e##n)

struct U64 {
  static constexpr u64 POW10[] = {U(0),  U(1),  U(2),  U(3),  U(4),  U(5),  U(6),
                                  U(7),  U(8),  U(9),  U(10), U(11), U(12), U(13),
                                  U(14), U(15), U(16), U(17), U(18), U(19)};

  [[sfc_inline]] static auto count(u64 val) -> u32 {
    const auto s = __builtin_clzll(val | 1);
    const auto n = (sizeof(u64) * 8 - s) * 1233 >> 12;
    const auto r = n + 1 - ((val | 1) < POW10[n] ? 1 : 0);
    return r;
  }
};

struct F64 {
  static constexpr f64 POW10[32] = {1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,   //
                                    1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,  //
                                    1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,  //
                                    1e30, 1e31};

  static constexpr auto MAX_PREC = u32(sizeof(POW10) / sizeof(POW10[0]));
};

struct Fix {
  u64 _int_val;
  u64 _flt_val;
  u32 _int_cnt;
  u32 _flt_cnt;
  u32 _dot_cnt;

  Fix(f64 val, u32 precision) {
    if (precision >= F64::MAX_PREC) {
      precision = F64::MAX_PREC;
    }

    if (precision == 0) {
      _int_val = u64(__builtin_round(val));
      _int_cnt = U64::count(_int_val);
      _flt_val = 0;
      _flt_cnt = 0;
      _dot_cnt = 0;
      return;
    }

    const auto fix_mul = F64::POW10[precision];
    const auto fix_val = u64(__builtin_round(val * fix_mul));
    _int_val = fix_val / u64(fix_mul);
    _int_cnt = U64::count(_int_val);
    _flt_val = fix_val % u64(fix_mul);
    _flt_cnt = precision;
    _dot_cnt = 1;
  }

  auto count() const -> u32 {
    return _int_cnt + _flt_cnt + _dot_cnt;
  }
};

struct Flt {
  u64 _int_val;
  u64 _flt_val;
  u32 _int_cnt;
  u32 _flt_cnt;
  u32 _dot_cnt;

  Flt(f64 val, u32 precision) {
    if (precision >= F64::MAX_PREC) {
      precision = F64::MAX_PREC;
    }

    _dot_cnt = 1;
    _int_val = u64(val);
    _int_cnt = U64::count(_int_val);
    if (_int_cnt >= precision) {
      _int_val = u64(__builtin_round(val));
      _int_cnt = U64::count(_int_val);
      _flt_val = 0;
      _flt_cnt = 0;
      return;
    }

    // 12.345 -> 12'3x
    // 12'3x  -> {12, 3x, 0}
    _flt_cnt = precision - _int_cnt;
    const auto flt_mul = F64::POW10[_flt_cnt];
    const auto fix_val = u64(__builtin_round(val * flt_mul));
    _int_val = fix_val / U64::POW10[_flt_cnt];
    _int_cnt = U64::count(_int_val);
    _flt_val = fix_val - _int_val * U64::POW10[_flt_cnt];
    _flt_cnt = precision - _int_cnt;
  }

  auto count() const -> u32 {
    return _int_cnt + _flt_cnt + _dot_cnt;
  }
};

struct Nan {
  Str _val;

  auto count() const -> u32 {
    return u32(_val.len());
  }
};

struct FmtFlt {
  char* _buf;
  usize _cap;
  usize _len;
  const Style& _style;

  FmtFlt(Slice<char> buf, const Style& style)
      : _buf{buf._ptr}, _cap{buf._len}, _len{0}, _style{style} {}

  auto to_str() const -> Str {
    return {_buf, _len};
  }

  void _write_chars(char c, usize cnt) {
    for (auto end = _len + cnt; _len != end; _len++) {
      _buf[_len] = c;
    }
  }

  void _write_sign(bool is_neg) {
    if (is_neg) {
      _buf[_len++] = '-';
      return;
    }

    switch (_style._sign) {
      case '-':
        _buf[_len++] = ' ';
        break;
      case '+':
        _buf[_len++] = '+';
        break;
      default:
        break;
    }
  }

  void _write_int(u64 val, usize cnt) {
    if (cnt == 0) {
      return;
    }

    static const auto DIGITS =
        "0001020304050607080910111213141516171819"
        "2021222324252627282930313233343536373839"
        "4041424344454647484950515253545556575859"
        "6061626364656667686970717273747576777879"
        "8081828384858687888990919293949596979899";

    auto b = _buf + _len - 1;
    auto p = b + cnt;
    for (; val >= 100; val /= 100) {
      *p-- = DIGITS[(val % 100) * 2 + 1];
      *p-- = DIGITS[(val % 100) * 2 + 0];
    }
    if (val < 10) {
      *p-- = '0' + char(val);
    } else {
      *p-- = DIGITS[val * 2 + 1];
      *p-- = DIGITS[val * 2 + 0];
    }
    while (p != b) {
      *p-- = '0';
    }
    _len += cnt;
  }

  void _write_val(const Fix& val) {
    this->_write_int(val._int_val, val._int_cnt);
    this->_write_chars('.', val._dot_cnt);
    this->_write_int(val._flt_val, val._flt_cnt);
  }

  void _write_val(const Flt& val) {
    this->_write_int(val._int_val, val._int_cnt);
    this->_write_chars('.', val._dot_cnt);
    this->_write_int(val._flt_val, val._flt_cnt);
  }

  void _write_val(const Nan& val) {
    for (auto x : val._val) {
      _buf[_len++] = x;
    }
  }

  void _write_flt(const auto& val, bool is_neg) {
    const auto cnt = val.count();
    const auto len = cmp::min<u32>(_style._width, _cap - 4);

    if (len <= cnt) {
      this->_write_sign(is_neg);
      this->_write_val(val);
      return;
    }

    const auto fcnt = len - cnt;
    const auto fill = _style._fill == 0 ? ' ' : _style._fill;
    switch (_style._align) {
      default:
      case '>':
        this->_write_chars(fill, fcnt);
        this->_write_sign(is_neg);
        this->_write_val(val);
        break;
      case '<':
        this->_write_sign(is_neg);
        this->_write_val(val);
        this->_write_chars(fill, fcnt);
        break;
      case '^':
        this->_write_chars(fill, (fcnt + 0) / 2);
        this->_write_sign(is_neg);
        this->_write_val(val);
        this->_write_chars(fill, (fcnt + 1) / 2);
        break;
      case '=':
        this->_write_sign(is_neg);
        this->_write_chars(fill, fcnt);
        this->_write_val(val);
        break;
    }
  }

  void pad_flt(auto val, bool is_neg) {
    if (::__builtin_isinf(val)) {
      return this->_write_flt(Nan{"inf"}, val < 0);
    } else if (::__builtin_isnan(val)) {
      return this->_write_flt(Nan{"Nan"}, false);
    }

    const auto prec = _style.precision(6);
    switch (_style._type) {
      default:
        return this->_write_flt(Flt(val, prec), is_neg);
      case 'f':
      case 'F':
        return this->_write_flt(Fix(val, prec), is_neg);
    }
  }
};

template <>
auto Style::_pad_num(f64 val, Slice<char> buf) const -> Str {
  auto f = FmtFlt{buf, *this};
  f.pad_flt(val < 0 ? -val : +val, val < 0);
  return f.to_str();
}

}  // namespace sfc::fmt
